package com.mgs.product.tagger.controller
{
	import com.mgs.fwk.context.ApplicationContext;
	import com.mgs.fwk.controller.ActionDispatcher;
	import com.mgs.fwk.util.StringUtils;
	import com.mgs.product.tagger.actions.AccessCellAction;
	import com.mgs.product.tagger.actions.ApplicationAction;
	import com.mgs.product.tagger.actions.AxisClearSelectionAction;
	import com.mgs.product.tagger.actions.AxisCreationAction;
	import com.mgs.product.tagger.actions.ClassificationSaveAction;
	import com.mgs.product.tagger.actions.DeleteAxisAction;
	import com.mgs.product.tagger.actions.DeleteDocumentAction;
	import com.mgs.product.tagger.actions.DocumentCreationAction;
	import com.mgs.product.tagger.actions.DocumentOpenAction;
	import com.mgs.product.tagger.actions.InitAction;
	import com.mgs.product.tagger.actions.LockElementsAction;
	import com.mgs.product.tagger.actions.NextCellAction;
	import com.mgs.product.tagger.actions.PreviousCellAction;
	import com.mgs.product.tagger.actions.ReleaseAllElementsAction;
	import com.mgs.product.tagger.actions.ReleaseElementsAction;
	import com.mgs.product.tagger.actions.SaveCellDescriptionAction;
	import com.mgs.product.tagger.actions.SegmentCreationAction;
	import com.mgs.product.tagger.actions.SegmentSelectionAction;
	import com.mgs.product.tagger.actions.SwitchRepositoryAction;
	import com.mgs.product.tagger.controller.util.SegmentFilter;
	import com.mgs.product.tagger.dao.ClassificationDao;
	import com.mgs.product.tagger.dao.ClassificationDaoImpl;
	import com.mgs.product.tagger.dao.DocumentDao;
	import com.mgs.product.tagger.dao.DocumentDaoImpl;
	import com.mgs.product.tagger.dao.PreferencesDao;
	import com.mgs.product.tagger.dao.PreferencesDaoImpl;
	import com.mgs.product.tagger.handlers.AccessCellHandler;
	import com.mgs.product.tagger.handlers.DeleteAxisHandler;
	import com.mgs.product.tagger.handlers.DeleteDocumentHandler;
	import com.mgs.product.tagger.handlers.LockElementsHandler;
	import com.mgs.product.tagger.handlers.NextCellHandler;
	import com.mgs.product.tagger.handlers.PrevCellHandler;
	import com.mgs.product.tagger.handlers.ReleaseAllElementsHandler;
	import com.mgs.product.tagger.handlers.SaveCellDescriptionHandler;
	import com.mgs.product.tagger.handlers.SegmentCreationHandler;
	import com.mgs.product.tagger.handlers.SwitchRepositoryHandler;
	import com.mgs.product.tagger.model.Axis;
	import com.mgs.product.tagger.model.Classifiable;
	import com.mgs.product.tagger.model.Classification;
	import com.mgs.product.tagger.model.Document;
	import com.mgs.product.tagger.model.Preferences;
	import com.mgs.product.tagger.model.Segment;
	import com.mgs.product.tagger.ui.dto.AxisUI;
	import com.mgs.product.tagger.ui.dto.ClassifiableUI;
	import com.mgs.product.tagger.ui.dto.DocumentUI;
	import com.mgs.product.tagger.ui.dto.DtoFactory;
	import com.mgs.product.tagger.ui.dto.HistoryUI;
	import com.mgs.product.tagger.ui.dto.MatrixUI;
	import com.mgs.product.tagger.ui.dto.NavigationContext;
	import com.mgs.product.tagger.ui.dto.SegmentUI;
	
	import flash.filesystem.File;
	import flash.utils.Dictionary;
	
	import mx.collections.ArrayList;
	import mx.collections.IList;
	import mx.utils.StringUtil;

	public class ContextController extends ActionDispatcher
	{
		public static const instance:ContextController = new ContextController();
		
		/**
		 * dep DocumentDao
		 */
		internal static var documentDao:DocumentDao = new DocumentDaoImpl();
		
		/**
		 * dep DocumentDao
		 */
		internal static var classificationDao:ClassificationDao = new ClassificationDaoImpl();
		
		/**
		 * dep PreferencesDao
		 */
		internal static var preferencesDao:PreferencesDao = new PreferencesDaoImpl();
		
		
		public function ContextController()
		{
			super.register(AccessCellAction, AccessCellHandler);
			super.register(SegmentCreationAction, SegmentCreationHandler);
			super.register(LockElementsAction, LockElementsHandler);
			super.register(ReleaseAllElementsAction, ReleaseAllElementsHandler);
			super.register(SaveCellDescriptionAction, SaveCellDescriptionHandler);
			super.register(DeleteDocumentAction, DeleteDocumentHandler);
			super.register(PreviousCellAction, PrevCellHandler);
			super.register(NextCellAction, NextCellHandler);
			super.register(DeleteAxisAction, DeleteAxisHandler);
			super.register(SwitchRepositoryAction, SwitchRepositoryHandler);
		}
		
		public static function createContext(repository:File, context:NavigationContext):void
		{
			var dictionary:Dictionary = new Dictionary();
			var nc:NavigationContext = context;
			nc.repository = repository;
			nc.classification = classificationDao.load(repository);
			nc.activeTags = new Vector.<Segment>();
			nc.axis = new ArrayList();
			nc.history = new HistoryUI();
			nc.history.historyCells = new ArrayList();
			nc.history.historyCells.addItem(new Vector.<Segment>());
			nc.history.currentIndex = 0;
			nc.elements = new ArrayList();
			nc.lockedElements = new ArrayList();
			for each(var element:Classifiable in nc.classification.elements)
			{
				var elementUI:ClassifiableUI;
				if(element is Axis)
				{
					elementUI = DtoFactory.createAxis(element as Axis);
					nc.axis.addItem(elementUI as AxisUI);
				}
				else
				{
					elementUI = DtoFactory.createDocument(element as Document)
					dictionary[(element as Document).uniqueCode] = element;
				}
				nc.elements.addItem(elementUI);
			}
			
			// load extra files.
			for each(var workspace:String in context.classification.workspaces)
			{
				var workspaceFile:File = new File(workspace);
				for each(var itemFile:File in workspaceFile.getDirectoryListing())
				{
					var path:String = itemFile.name;
					var item:Document = dictionary[path] as Document;
					if(item == null)
					{
						var doc:Document = new Document();
						doc.tags = new Vector.<Segment>();
						doc.name = path;
						doc.file = itemFile;
						doc.uniqueCode = path;
						doc.isPersistent = false;
						context.classification.elements.push(doc);
					}
					else
					{
						item.file = workspaceFile.resolvePath(item.uniqueCode);
					}
				}
			}

			
			updateContext(context);
		}
		
		public static function updateContext(context:NavigationContext):void
		{
			while(updateContextAxis(context))
			{
				
			}
			// set segment selection on axis
			for each(var axisUI:AxisUI in context.axis.toArray())
			{
				axisUI.selectedSegments = new ArrayList();
				for each(var segment:SegmentUI in axisUI.segments.toArray())
				{
					if(context.activeTags.indexOf(segment.segment) >= 0)
					{
						axisUI.selectedSegments.addItem(segment);
					}
				}
			}
			updateContextElements(context);
			updateContextSegments(context);
			updateContextDescription(context);
			// update matrix :
			context.matrix = new MatrixUI(context);
		}
		
		public static function updateContextDescription(context:NavigationContext):void
		{
			var doc:Document;
			var docCount:int = 0;
			for each(var element:ClassifiableUI in context.elements.toArray())
			{
				if(element is DocumentUI)
				{
					// same size and same tagging
					var sameTags:Boolean =  context.activeTags.length == (element as DocumentUI).document.tags.length &&
						context.activeTags.every(function (item:*, index:int, array:Vector.<Segment>):Boolean {
							return (element as DocumentUI).document.tags.indexOf(item) >= 0;
						});

					if(sameTags){
						if((element as DocumentUI).document.name == "desc.txt")
						{
							doc = (element as DocumentUI).document;
							docCount = 1;
							break;
						}
						if(StringUtils.endWith((element as DocumentUI).document.name, ".txt"))
						{
							doc = (element as DocumentUI).document;
							docCount++;
						}
					}
				}
			}
			if(doc != null && docCount == 1)
			{
				if(doc.content == null)
				{
					doc.content = documentDao.readDocumentContent(doc)
				}
			}
			context.cell = DtoFactory.createCell(doc);
		}
		
		private static function updateContextSegments(context:NavigationContext):void
		{
			var vAll:Vector.<Classifiable> = new Vector.<Classifiable>();
			for each(var element:ClassifiableUI in context.elements.toArray())
			{
				vAll.push(element.element);
			}
			for each(var axisUI:AxisUI in context.axis.toArray())
			{
				for each(var segment:SegmentUI in axisUI.segments.toArray())
				{
					if(context.activeTags.length == 0)
					{
						segment.contextualElements = segment.segment.taggedElements.length;
					}
					else
					{
						segment.contextualElements = interSegments(segment.segment.taggedElements, vAll).length;
					}
				}
			}
		}
		
		private static function interSegments(a:Vector.<Classifiable>, b:Vector.<Classifiable>):Vector.<Classifiable>
		{
			return a.filter(
				function(item:*, index:int, array:Vector.<Classifiable>):Boolean{
					return b.indexOf(item as Classifiable) >= 0;
				}
			);
		}
		
		private static function updateContextAxis(context:NavigationContext):Boolean
		{
			var i:int;
			var index:int;
			var filteredElements:Vector.<Axis> = new Vector.<Axis>();
			if(context.activeTags.length == 0)
			{
				for each(var element:Classifiable in context.classification.elements)
				{
					if(element is Axis){
						filteredElements.push(element);
					}
				}
			}
			else
			{
				for each(var segment:Segment in context.activeTags)
				{
					for each(element in segment.taggedElements)
					{
						if(element is Axis){
							index = filteredElements.indexOf(element);
							if(index < 0)
							{
								filteredElements.push(element);
							}
						}
					}
				}
			}
			
			//remove not matching
			for(i=0 ; i<filteredElements.length ; )
			{
				var currentAxis:Axis = filteredElements[i];
				var match:Boolean = true;
				for each(var tag:Segment in currentAxis.tags)
				{
					if(context.activeTags.indexOf(tag) < 0)
					{
						match = false;
						break;
					}
				}
				if(match)
				{
					i++;
				}
				else
				{
					filteredElements.splice(i, 1);
				}
			}
			
			// UPDATE UI
			
			// remove no longer existing (sauf les non taggués)
			var activeTagsChanged:Boolean = false;
			for(i = 0 ; i<context.axis.length ; )
			{
				var axis:AxisUI = context.axis.getItemAt(i) as AxisUI;
				index = filteredElements.indexOf(axis.axis);
				if(index < 0 && axis.axis.tags.length != 0)
				{
					context.axis.removeItemAt(i);
					if(clearAxis(context, axis.axis))
					{
						activeTagsChanged = true;
					}
				}
				else
				{
					if(index >= 0){
						filteredElements.splice(index, 1);
					}
					i++;
				}
			}
			// add axis
			for each(element in filteredElements)
			{
				if(element is Axis)
				{
					context.axis.addItem(DtoFactory.createAxis(element as Axis));
				}
			}
			return activeTagsChanged;
		}
		
		public static function updateContextElements(context:NavigationContext):void
		{
			var filteredElements:Vector.<Classifiable> = null;
			if(context.activeTags.length == 0)
			{
				filteredElements = context.classification.elements;
			}
			else
			{
				var element:Classifiable;
				for each(var segment:Segment in context.activeTags)
				{
					if(filteredElements == null)
					{
						filteredElements = new Vector.<Classifiable>();
						for each(element in segment.taggedElements)
						{
							filteredElements.push(element);
						}
					}
					else
					{
						var filter:SegmentFilter = new SegmentFilter(segment);
						filteredElements = filteredElements.filter(filter.filterFunction, filter);
					}
				}
			}
			var lstElement:IList = new ArrayList();
			if(context.lockedElements != null)
			{
				for each(var elementUI:ClassifiableUI in context.lockedElements.toArray())
				{
					lstElement.addItem(elementUI);
					var index:int = filteredElements.indexOf(elementUI.element);
					if(index >= 0)
					{
						filteredElements.splice(index, 1);
					}
				}
			}
			
			for each(element in filteredElements)
			{
				var elementUI2:ClassifiableUI;
				if(element is Axis)
				{
					elementUI2 = DtoFactory.createAxis(element as Axis);
				}
				else
				{
					elementUI2 = DtoFactory.createDocument(element as Document);
				}
				lstElement.addItem(elementUI2);
			}
			
			context.elements = lstElement; 
		}
		
		public static function retagElement(context:NavigationContext, elementUI:ClassifiableUI):void
		{
			var element:Classifiable = elementUI.element;
			// untag element
			if(element.tags.length == 0)
			{
				context.classification.elements.splice(context.classification.elements.indexOf(element), 1);
			}
			else
			{
				for each(var tag:Segment in element.tags)
				{
					tag.taggedElements.splice(tag.taggedElements.indexOf(element), 1);
				}
				element.tags = new Vector.<Segment>();
			}
			// tag element
			tagElement(context, element);
		}
		
		private static function tagElement(context:NavigationContext, element:Classifiable):void
		{
			if(context.activeTags.length == 0)
			{
				context.classification.elements.push(element);
			}
			else
			{
				for each(var segment:Segment in context.activeTags)
				{
					segment.taggedElements.push(element);
					element.tags.push(segment);
				}
			}
			
		}
		
		override public function processAction(context:ApplicationContext, action:ApplicationAction):void
		{
			if(!processEvent(context as NavigationContext, action))
			{
				super.processAction(context, action);
			}
			// update context if needed.
			if((context as NavigationContext).invalidActiveTags)
			{
				updateContext((context as NavigationContext));
				(context as NavigationContext).invalidActiveTags = false;
				// history update ...
				if(!(context as NavigationContext).history.historyLocked)
				{
					while((context as NavigationContext).history.historyCells.length > (context as NavigationContext).history.currentIndex + 1){
						(context as NavigationContext).history.historyCells.removeItemAt((context as NavigationContext).history.currentIndex + 1);
					}
					(context as NavigationContext).history.historyCells.addItem((context as NavigationContext).activeTags.concat());
					(context as NavigationContext).history.currentIndex++;
				}
			}
		}
		
		private function processEvent(context:NavigationContext, action:ApplicationAction):Boolean
		{
			// TODO split all in separated handlers
			// process event.
			if(action is AxisCreationAction)
			{
				// tagging de l'axe
				(action as AxisCreationAction).axis.axis.tags
				context.axis.addItem((action as AxisCreationAction).axis);
				context.elements.addItem((action as AxisCreationAction).axis);
				tagElement(context, (action as AxisCreationAction).axis.axis);
			}
			else if(action is SegmentSelectionAction)
			{
				// on ajoute le tag au context que si celui-ci n'est pas présent.
				if(context.activeTags.indexOf((action as SegmentSelectionAction).segment.segment) < 0)
				{
					clearAxis(context, (action as SegmentSelectionAction).segment.segment.axis);
					context.activeTags.push((action as SegmentSelectionAction).segment.segment);
					context.invalidateActiveTags();
				}
			}
			else if(action is AxisClearSelectionAction)
			{
				if(clearAxis(context, (action as AxisClearSelectionAction).axis.axis))
				{
					context.invalidateActiveTags();
				}
			}
			else if(action is DocumentCreationAction)
			{
				// create doc
				documentDao.createDocument(context.repository, (action as DocumentCreationAction).document.document);
				context.elements.addItem((action as DocumentCreationAction).document);
				tagElement(context, (action as DocumentCreationAction).document.document);
			}
			else if(action is DocumentOpenAction)
			{
				// create doc
				documentDao.openDocument(context.repository, (action as DocumentOpenAction).document.document);
			}
			else if(action is ClassificationSaveAction)
			{
				// create doc
				classificationDao.save(context.repository, context.classification);
				preferencesDao.save(context.preferences);
			}
			else if(action is ReleaseElementsAction)
			{
				for each(var element:ClassifiableUI in (action as ReleaseElementsAction).elements)
				{
					retagElement(context, element);
					var index:int = context.lockedElements.getItemIndex(element);
					if(index >= 0)
					{
						context.lockedElements.removeItemAt(index);
					}
					element.locked = false;
				}
				// TODO optim : seuelement si des axes ont été retagués.
				updateContext(context);
			}
			else if(action is InitAction)
			{
				// load preferences
				context.preferences = preferencesDao.load();
				if(context.preferences.defaultRepository != null)
				{
					// load default context.
					createContext(context.preferences.defaultRepository, context);
				}
			}
			else
			{
				return false;
			}
			return true;
		}
		
		private static function clearAxis(context:NavigationContext, axis:Axis):Boolean
		{
			var result:Boolean = false;
			for each(var segment:Segment in axis.segments)
			{
				var index:int = context.activeTags.indexOf(segment);
				if(index >= 0)
				{
					context.activeTags.splice(index, 1);
					result = true;
				}
			}
			return result;
		}
	}
}